package com.easy.dev.utils;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.easy.dev.annotation.Bind;
import com.easy.dev.entity.Service;
import org.springframework.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

public class MysqlSqlUtils implements SqlUtil {

    /**
     * 根据对象获取对象所有字段，并封装成sql语句
     * @param clazz
     * @param prefix 如果有前缀，每个属性前面需加上属性
     * @param join 是否含有关联表
     * @return
     */
    public static String createFieldsByObject(Class clazz, String prefix, boolean join) {
        boolean preFlag = true;
        if (StringUtils.isEmpty(prefix)) {
            preFlag = false;
        }
        Field[] fields = clazz.getDeclaredFields();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < fields.length; i++) {
            fields[i].setAccessible(true);
            boolean isStatic = Modifier.isStatic(fields[i].getModifiers());
            if (isStatic) continue; // 过滤静态字段
            Bind bind = fields[i].getAnnotation(Bind.class);
            String name = null;
            if (join && bind != null) { // 如果存在关联表则应该使用真实展示字段
                name = bind.realColumn();
            } else {
                TableField tableField = fields[i].getAnnotation(TableField.class);
                if (tableField == null || tableField.value() == null) {
                    name = fields[i].getName();
                } else {
                    name = tableField.value();
                }
            }
            if (preFlag) {
                sb.append(prefix).append(".");
            }
            sb.append(name).append(",");
        }
        // 去除后缀多余的逗号
        return sb.substring(0,sb.length()-1);
    }

    /**
     * 根据Class 获取该类的所有字段名称，并用逗号分割组装称查询字段
     * @param clazz
     * @return
     */
    public static String getSelectFieldsByClass(Class clazz) {
        StringBuilder sb = new StringBuilder();
        Field[] fields = ClassUtils.getFields(clazz);
        sb.append(FieldUtils.getSqlFieldName(fields[0]));
        for (int i = 1; i < fields.length; i++) {
            sb.append(",").append(FieldUtils.getSqlFieldName(fields[i]));
        }
        return sb.toString();
    }

    public static String getSelectFieldsByFields(Field[] fields, boolean join, List<String> tableAliasList) {
        StringBuilder sb = new StringBuilder();
        Bind bind0 = fields[0].getAnnotation(Bind.class);
        int index = 1;
        if (bind0 == null) {
            sb.append(tableAliasList.get(0)).append(".").append(FieldUtils.getSqlFieldName(fields[0], join, bind0));
        } else {
            sb.append(tableAliasList.get(index++)).append(".").append(FieldUtils.getSqlFieldName(fields[0], join, bind0));
        }
        for (int i = 1; i < fields.length; i++) {
            sb.append(",");
            Bind bind = fields[i].getAnnotation(Bind.class);
            if (bind == null) {
                sb.append(tableAliasList.get(0)).append(".");
            } else {
                sb.append(tableAliasList.get(index++)).append(".");
            }
            sb.append(FieldUtils.getSqlFieldName(fields[i], join, bind));
        }
        return sb.toString();
    }

    public static List<Class> getJoinClass(Field[] fields) {
        List<Class> clazzs = new ArrayList<>(fields.length);
        for (int i = 0; i < fields.length; i++) {
            Bind bind = fields[i].getAnnotation(Bind.class);
            if (bind != null) {
                clazzs.add(bind.clazz());
            }
        }
        return clazzs;
    }

    public static List<Bind> getJoinBind(Field[] fields) {
        List<Bind> binds = new ArrayList<>(fields.length);
        for (int i = 0; i < fields.length; i++) {
            Bind bind = fields[i].getAnnotation(Bind.class);
            if (bind != null) {
                binds.add(bind);
            }
        }
        return binds;
    }

    public static List<Field> getJoinField(Field[] fields) {
        List<Field> fieldList = new ArrayList<>(fields.length);
        for (int i = 0; i < fields.length; i++) {
            Bind bind = fields[i].getAnnotation(Bind.class);
            if (bind != null) {
                fieldList.add(fields[i]);
            }
        }
        return fieldList;
    }

    public static String getTableName(Class clazz) {
        TableName annotation = (TableName) clazz.getAnnotation(TableName.class);
        if (annotation == null) {
            return classNameToTableName(clazz.getSimpleName());
        }
        return annotation.value();
    }

    public static String classNameToTableName(String name) {
        // 暂时不支持驼峰转下划线表明
        return name.substring(0,1).toLowerCase()+name.substring(1);
    }


    public static String getSelectSql(Class clazz, boolean join) {
        Field[] fields = ClassUtils.getFields(clazz);
        List<Class> classList = null;
        List<Bind> bindList = null;
        List<Field> fieldList = null;
        if (join) {
            classList = getJoinClass(fields);
            bindList = getJoinBind(fields);
            fieldList = getJoinField(fields);
        }
        List<String> tableAliasList = null;
        if (!CollectionUtils.isEmpty(classList)) {
            tableAliasList = getTableAlias(clazz, classList);
        }
        StringBuilder sb = new StringBuilder(SELECT);
        sb.append(getSelectFieldsByFields(fields, join, tableAliasList));
        sb.append(FROM);
        String alias = tableAliasList.get(0);
        sb.append(getTableName(clazz)).append(" ").append(alias);
        for (int i = 0; i < fieldList.size(); i++) {
            Field field = fieldList.get(i);
            Bind bind = field.getAnnotation(Bind.class);
            String tableAlias = tableAliasList.get(i+1);
            sb.append(bind.join()).append(getTableName(bind.clazz())).append(" ").append(tableAlias);
            sb.append(" ON ")
                    .append(alias).append(".").append(FieldUtils.getSqlFieldName(field))
                    .append(" = ")
                    .append(tableAlias).append(".").append(bind.joinColumn());
        }
        return sb.toString();
    }

    private static List<String> getTableAlias(Class clazz, List<Class> clazzList) {
        String name = getTableName(clazz);
        int length = clazzList.size()+1;
        LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>(length);
        List<String> aliasList = new ArrayList<>(length);
        linkedHashMap.put(name.substring(0,1),1);
        clazzList.forEach(clazzz -> {
            String clazzName = getTableName(clazzz).substring(0,1);
            if (linkedHashMap.containsValue(clazzName)) {
                Integer index = 1;
                while (linkedHashMap.containsValue(clazzName+index)) {
                    index++;
                }
                linkedHashMap.put(clazzName+index, 1);
            } else {
                linkedHashMap.put(clazzName, 1);
            }
        });
        linkedHashMap.forEach((key, value) -> {
            aliasList.add(key);
        });
        return aliasList;
    }

    public static void main(String[] args) {
        System.out.println(getSelectSql(Service.class, true));
    }
}
